#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "bubblefs.h"

struct bubblefs_superblock sb;
FILE* fp;
uint64_t free_block_start;
uint64_t free_inode_start = 0;

void read_block(int bno, void* buf) {
        fseek(fp, BLOCKSIZE * bno, SEEK_SET);
        fread(buf, sizeof(char), BLOCKSIZE / sizeof(char), fp);
}

void write_block(int bno, void* buf) {
        fseek(fp, BLOCKSIZE * bno, SEEK_SET);
        fwrite(buf, sizeof(char), BLOCKSIZE / sizeof(char), fp);
}

void read_inode(uint64_t inode_idx, struct bubblefs_inode* inode) {
        char buf[BLOCKSIZE];
        uint64_t bno = sb.inodes_offset + (inode_idx / INODES_PER_BLOCK);
        read_block(bno, buf);

        struct bubblefs_inode* inodes = (struct bubblefs_inode*)buf;
        *inode = inodes[inode_idx % INODES_PER_BLOCK];
}

void write_inode(uint64_t inode_idx, struct bubblefs_inode* inode) {
        char buf[BLOCKSIZE];
        uint64_t bno = sb.inodes_offset + (inode_idx / INODES_PER_BLOCK);
        read_block(bno, buf);

        struct bubblefs_inode* inodes = (struct bubblefs_inode*)buf;
        inodes[inode_idx % INODES_PER_BLOCK] = *inode;
        write_block(bno, buf);
}

void alloc_inode(char* name, nlink_t link, mode_t mode) {
        char buf[BLOCKSIZE];
        uint64_t inode_idx = free_inode_start++;

        uint64_t bno = sb.inodes_offset + (inode_idx / INODES_PER_BLOCK);
        read_block(bno, buf);

        struct bubblefs_inode* inode = ((struct bubblefs_inode*)buf) + (inode_idx % INODES_PER_BLOCK);

        strcpy(inode->name, name);
        inode->size = 0;
        inode->used = 1;
        inode->file_stat.st_nlink = link;
        inode->file_stat.st_mode = mode;
        memset(inode->refs, 0, sizeof(inode->refs));

        write_inode(inode_idx, inode);

}

void mark_as_used(uint64_t used) {
        char buf[BLOCKSIZE];
        memset(buf, 0, BLOCKSIZE);
        
        for(int i = 0; i < used; i++) {
                buf[i / 8] = buf[i / 8] | (1 << (i % 8));
        }
        
        write_block(sb.free_map_offset, buf);
}

int main(int argc, char** argv) {
        char buf[BLOCKSIZE];

        if(argc != 2){
                fprintf(stderr, "Usage: mkfs <fs name>\n");
                return 1;
        }

        char* path = argv[1];
        fp = fopen(path, "wb");
        if(fp < 0) {
                fprintf(stderr, "can not open %s\n", path);
                return 1;
        }

        // create a empty fs
        memset(buf, 0, BLOCKSIZE);
        for(int i = 0; i < BLOCKNUMS; i++) {
                write_block(i, buf);
        }

        sb.magic = MAGICNUM;
        strcpy(sb.name, "BubbleFS");
        sb.size = TOTALSIZE;

        sb.inodes_offset = 1;
        sb.n_inodes = INODENUMS;
        sb.n_blocks = BLOCKNUMS;
        sb.free_map_offset = sb.inodes_offset + INODE_BLOCKS + 1;
        
        assert(sizeof(struct bubblefs_superblock) <= BLOCKSIZE);

        free_block_start = sb.free_map_offset + FREE_MAP_BLOCKS + 1;

        // alloc a root inode of index 0
        alloc_inode("root", 2, S_IFDIR | 0755);

        // struct bubblefs_inode root;

        // read_inode(0, &root);
        // root.size += 1;
        // root.refs[0].btype = DATABLOCK;
        // root.refs[0].offset = free_block_start++;

        // read_block(root.refs[0].offset, buf);
        // struct dirnode* dirs = (struct dirnode*)buf;

        // dirs[0].inum = 0;
        // strcpy(dirs[0].name, ".");
        // dirs[1].inum = 0;
        // strcpy(dirs[1].name, "..");

        // write_block(root.refs[0].offset, buf);

        mark_as_used(free_block_start);

        // commit superblock to fs
        memset(buf, 0, BLOCKSIZE);
        memcpy(buf, &sb, sizeof(struct bubblefs_superblock));

        write_block(0, buf);

        fclose(fp);

        return 0;
}
